import {
  matchesSelectorToParentElements,
  getComputedSize,
  getComputedPosition,
  addEvent,
  removeEvent
} from '../utils/dom'
import {
  computeWidth,
  computeHeight,
  restrictToBounds,
  snapToGrid
} from '../utils/fns'
import { mapState } from 'vuex'
import { throttle } from 'lodash-es'
const events = {
  mouse: {
    start: 'mousedown',
    move: 'mousemove',
    stop: 'mouseup'
  },
  touch: {
    start: 'touchstart',
    move: 'touchmove',
    stop: 'touchend'
  }
}
let eventsFor = events.mouse
export default {
  data() {
    return {
      parent: true,
      originalWidth: 0,
      originalHeight: 0,
      left: null,
      top: null,
      right: null,
      bottom: null,
      width: null,
      height: null,
      maxW: 0,
      maxH: 0,
      widthTouched: false,
      heightTouched: false,
      aspectFactor: null,
      parentWidth: null,
      parentHeight: null,
      handles: ['tl', 'tm', 'mr', 'br', 'bm', 'bl', 'ml'],
      enabled: false,
      resizing: false,
      dragging: false,
      dragEnable: false,
      resizeEnable: false,
      transformScale: ''
    }
  },

  created() {
    this.resetBoundsAndMouseState()
  },
  mounted() {
    this.$el.ondragstart = () => false
    let resetStyle = { top: '', left: '', right: '', bottom: '' }
    Object.assign(this.$el.style, this.anovPart.style)
    this.setAnovPartSize(this.$el)
    Object.assign(this.$el.style, resetStyle)
    addEvent(document.documentElement, 'mousedown', this.deselect)
    addEvent(document.documentElement, 'touchend touchcancel', this.deselect)
    addEvent(window, 'resize', throttle(this.checkParentSize, 300).bind(this))
  },
  beforeDestroy() {
    removeEvent(document.documentElement, 'mousedown', this.deselect)
    removeEvent(document.documentElement, 'touchstart', this.handleUp)
    removeEvent(document.documentElement, 'mousemove', this.move)
    removeEvent(document.documentElement, 'touchmove', this.move)
    removeEvent(document.documentElement, 'mouseup', this.handleUp)
    removeEvent(document.documentElement, 'touchend touchcancel', this.deselect)
    removeEvent(window, 'resize', this.checkParentSize)
  },
  methods: {
    setAnovPartSize($el) {
      let transformScaleNumArr = [1, 1]
      if (app.axis === 'relative') {
        const [parentWidth, parentHeight] = getComputedSize(this.$parent.$el)
        this.parentWidth = parentWidth
        this.parentHeight = parentHeight
      } else {
        this.parentWidth = app.envServer.designSize.w
        this.parentHeight = app.envServer.designSize.h
      }
      this.transformScale = this.anovPart.style.scale || ''
      if (this.transformScale) {
        transformScaleNumArr = this.transformScale.match(/\d+\.\d+/g)
      }
      const [width, height] = getComputedSize($el)
      const [left, top] = getComputedPosition($el)
      this.originalWidth = width
      this.originalHeight = height
      this.width = width * (transformScaleNumArr[0] || 1)
      this.height = height * (transformScaleNumArr[1] || 1)
      this.left = left
      this.top = top
      this.right = this.parentWidth - this.width - this.left
      this.bottom = this.parentHeight - this.height - this.top
      this.aspectFactor = this.width / this.height
    },
    resetBoundsAndMouseState() {
      this.mouseClickPosition = { mouseX: 0, mouseY: 0, x: 0, y: 0, w: 0, h: 0 }
      this.bounds = {
        minLeft: null,
        maxLeft: null,
        minRight: null,
        maxRight: null,
        minTop: null,
        maxTop: null,
        minBottom: null,
        maxBottom: null
      }
    },
    checkParentSize() {
      if (app.axis === 'relative') {
        const [parentWidth, parentHeight] = getComputedSize(this.$parent.$el)
        this.parentWidth = parentWidth
        this.parentHeight = parentHeight
      } else {
        this.parentWidth =
          app.envServer.designSize.w || app.envServer.designSize.width
        this.parentHeight =
          app.envServer.designSize.h || app.envServer.designSize.height
      }
      this.right = this.parentWidth - this.width - this.left
      this.bottom = this.parentHeight - this.height - this.top
    },
    elementTouchDown(e) {
      if (!this.isDragOpen) return
      eventsFor = events.touch
      this.elementDown(e)
    },
    elementMouseDown(e) {
      if (!this.isDragOpen) return
      eventsFor = events.mouse
      this.elementDown(e)
    },
    elementDown(e) {
      if (e instanceof MouseEvent && e.which !== 1) {
        return
      }
      const target = e.target || e.srcElement
      if (this.$el.contains(target)) {
        // if (this.onDragStart(e) === false) {
        //   return
        // }
        if (
          (this.dragHandle &&
            !matchesSelectorToParentElements(
              target,
              this.dragHandle,
              this.$el
            )) ||
          (this.dragCancel &&
            matchesSelectorToParentElements(target, this.dragCancel, this.$el))
        ) {
          this.dragging = false
          return
        }
        if (!this.enabled) {
          this.enabled = true
          this.$emit('activated')
          this.$emit('update:active', true)
        }
        if (this.draggable) {
          this.dragEnable = true
        }
        this.mouseClickPosition.mouseX = e.touches
          ? e.touches[0].pageX
          : e.pageX
        this.mouseClickPosition.mouseY = e.touches
          ? e.touches[0].pageY
          : e.pageY

        this.mouseClickPosition.left = this.left
        this.mouseClickPosition.right = this.right
        this.mouseClickPosition.top = this.top
        this.mouseClickPosition.bottom = this.bottom
        if (this.parent) {
          this.bounds = this.calcDragLimits()
        }

        addEvent(document.documentElement, eventsFor.move, this.move)
        addEvent(document.documentElement, eventsFor.stop, this.handleUp)
      }
    },
    calcDragLimits() {
      return {
        minLeft: this.left % this.grid[0],
        maxLeft:
          Math.floor(
            (this.parentWidth - this.width - this.left) / this.grid[0]
          ) *
            this.grid[0] +
          this.left,
        minRight: this.right % this.grid[0],
        maxRight:
          Math.floor(
            (this.parentWidth - this.width - this.right) / this.grid[0]
          ) *
            this.grid[0] +
          this.right,
        minTop: this.top % this.grid[1],
        maxTop:
          Math.floor(
            (this.parentHeight - this.height - this.top) / this.grid[1]
          ) *
            this.grid[1] +
          this.top,
        minBottom: this.bottom % this.grid[1],
        maxBottom:
          Math.floor(
            (this.parentHeight - this.height - this.bottom) / this.grid[1]
          ) *
            this.grid[1] +
          this.bottom
      }
    },
    deselect(e) {
      const target = e.target || e.srcElement
      const regex = new RegExp(this.className + '-([trmbl]{2})', '')

      if (!this.$el.contains(target) && !regex.test(target.className)) {
        if (this.enabled && !this.preventDeactivation) {
          this.enabled = false
          this.$emit('deactivated')
          this.$emit('update:active', false)
        }
        removeEvent(document.documentElement, eventsFor.move, this.handleResize)
      }

      this.resetBoundsAndMouseState()
    },
    handleTouchDown(handle, e) {
      eventsFor = events.touch

      this.handleDown(handle, e)
    },
    handleDown(handle, e) {
      if (e instanceof MouseEvent && e.which !== 1) {
        return
      }
      // if (this.onResizeStart(handle, e) === false) {
      //   return
      // }
      if (e.stopPropagation) e.stopPropagation()

      // Here we avoid a dangerous recursion by faking
      // corner handles as middle handles
      if (this.lockAspectRatio && !handle.includes('m')) {
        this.handle = 'm' + handle.substring(1)
      } else {
        this.handle = handle
      }
      this.resizeEnable = true
      this.mouseClickPosition.mouseX = e.touches ? e.touches[0].pageX : e.pageX
      this.mouseClickPosition.mouseY = e.touches ? e.touches[0].pageY : e.pageY
      this.mouseClickPosition.left = this.left
      this.mouseClickPosition.right = this.right
      this.mouseClickPosition.top = this.top
      this.mouseClickPosition.bottom = this.bottom
      this.bounds = this.calcResizeLimits()
      addEvent(document.documentElement, eventsFor.move, this.handleResize)
      addEvent(document.documentElement, eventsFor.stop, this.handleUp)
    },
    calcResizeLimits() {
      let minW = this.minW
      let minH = this.minH
      let maxW = this.maxW
      let maxH = this.maxH

      const aspectFactor = this.aspectFactor
      const [gridX, gridY] = this.grid
      const width = this.width
      const height = this.height
      const left = this.left
      const top = this.top
      const right = this.right
      const bottom = this.bottom

      if (this.lockAspectRatio) {
        if (minW / minH > aspectFactor) {
          minH = minW / aspectFactor
        } else {
          minW = aspectFactor * minH
        }

        if (maxW && maxH) {
          maxW = Math.min(maxW, aspectFactor * maxH)
          maxH = Math.min(maxH, maxW / aspectFactor)
        } else if (maxW) {
          maxH = maxW / aspectFactor
        } else if (maxH) {
          maxW = aspectFactor * maxH
        }
      }

      maxW = maxW - (maxW % gridX)
      maxH = maxH - (maxH % gridY)

      const limits = {
        minLeft: null,
        maxLeft: null,
        minTop: null,
        maxTop: null,
        minRight: null,
        maxRight: null,
        minBottom: null,
        maxBottom: null
      }

      if (this.parent) {
        limits.minLeft = left % gridX
        limits.maxLeft = left + Math.floor((width - minW) / gridX) * gridX
        limits.minTop = top % gridY
        limits.maxTop = top + Math.floor((height - minH) / gridY) * gridY
        limits.minRight = right % gridX
        limits.maxRight = right + Math.floor((width - minW) / gridX) * gridX
        limits.minBottom = bottom % gridY
        limits.maxBottom = bottom + Math.floor((height - minH) / gridY) * gridY

        if (maxW) {
          limits.minLeft = Math.max(
            limits.minLeft,
            this.parentWidth - right - maxW
          )
          limits.minRight = Math.max(
            limits.minRight,
            this.parentWidth - left - maxW
          )
        }

        if (maxH) {
          limits.minTop = Math.max(
            limits.minTop,
            this.parentHeight - bottom - maxH
          )
          limits.minBottom = Math.max(
            limits.minBottom,
            this.parentHeight - top - maxH
          )
        }

        if (this.lockAspectRatio) {
          limits.minLeft = Math.max(limits.minLeft, left - top * aspectFactor)
          limits.minTop = Math.max(limits.minTop, top - left / aspectFactor)
          limits.minRight = Math.max(
            limits.minRight,
            right - bottom * aspectFactor
          )
          limits.minBottom = Math.max(
            limits.minBottom,
            bottom - right / aspectFactor
          )
        }
      } else {
        limits.minLeft = null
        limits.maxLeft = left + Math.floor((width - minW) / gridX) * gridX
        limits.minTop = null
        limits.maxTop = top + Math.floor((height - minH) / gridY) * gridY
        limits.minRight = null
        limits.maxRight = right + Math.floor((width - minW) / gridX) * gridX
        limits.minBottom = null
        limits.maxBottom = bottom + Math.floor((height - minH) / gridY) * gridY

        if (maxW) {
          limits.minLeft = -(right + maxW)
          limits.minRight = -(left + maxW)
        }

        if (maxH) {
          limits.minTop = -(bottom + maxH)
          limits.minBottom = -(top + maxH)
        }

        if (this.lockAspectRatio && maxW && maxH) {
          limits.minLeft = Math.min(limits.minLeft, -(right + maxW))
          limits.minTop = Math.min(limits.minTop, -(maxH + bottom))
          limits.minRight = Math.min(limits.minRight, -left - maxW)
          limits.minBottom = Math.min(limits.minBottom, -top - maxH)
        }
      }

      return limits
    },
    move(e) {
      if (this.resizing) {
        this.handleResize(e)
      } else if (this.dragEnable) {
        this.handleDrag(e)
      }
    },
    handleDrag(e) {
      const axis = this.axis
      const grid = this.grid
      const bounds = this.bounds
      const mouseClickPosition = this.mouseClickPosition

      const tmpDeltaX =
        axis && axis !== 'y'
          ? mouseClickPosition.mouseX -
            (e.touches ? e.touches[0].pageX : e.pageX)
          : 0
      const tmpDeltaY =
        axis && axis !== 'x'
          ? mouseClickPosition.mouseY -
            (e.touches ? e.touches[0].pageY : e.pageY)
          : 0

      const [deltaX, deltaY] = snapToGrid(
        grid,
        tmpDeltaX,
        tmpDeltaY,
        this.scale
      )

      const left = restrictToBounds(
        mouseClickPosition.left - deltaX,
        bounds.minLeft,
        bounds.maxLeft
      )
      const top = restrictToBounds(
        mouseClickPosition.top - deltaY,
        bounds.minTop,
        bounds.maxTop
      )

      // if (this.onDrag(left, top) === false) {
      //   return
      // }

      const right = restrictToBounds(
        mouseClickPosition.right + deltaX,
        bounds.minRight,
        bounds.maxRight
      )
      const bottom = restrictToBounds(
        mouseClickPosition.bottom + deltaY,
        bounds.minBottom,
        bounds.maxBottom
      )

      this.left = left
      this.top = top
      this.right = right
      this.bottom = bottom
      // this.$emit('dragging', this.left, this.top)
      this.dragging = true
    },
    moveHorizontally(val) {
      // should calculate with scale 1.
      const [deltaX, _] = snapToGrid(this.grid, val, this.top, 1)
      const left = restrictToBounds(
        deltaX,
        this.bounds.minLeft,
        this.bounds.maxLeft
      )

      this.left = left
      this.right = this.parentWidth - this.width - left
    },
    moveVertically(val) {
      // should calculate with scale 1.
      const [_, deltaY] = snapToGrid(this.grid, this.left, val, 1)

      const top = restrictToBounds(
        deltaY,
        this.bounds.minTop,
        this.bounds.maxTop
      )

      this.top = top
      this.bottom = this.parentHeight - this.height - top
    },
    handleResize(e) {
      let left = this.left
      let top = this.top
      let right = this.right
      let bottom = this.bottom
      const mouseClickPosition = this.mouseClickPosition
      const aspectFactor = this.aspectFactor
      const tmpDeltaX =
        mouseClickPosition.mouseX - (e.touches ? e.touches[0].pageX : e.pageX)
      const tmpDeltaY =
        mouseClickPosition.mouseY - (e.touches ? e.touches[0].pageY : e.pageY)

      if (!this.widthTouched && tmpDeltaX) {
        this.widthTouched = true
      }

      if (!this.heightTouched && tmpDeltaY) {
        this.heightTouched = true
      }

      const [deltaX, deltaY] = snapToGrid(
        this.grid,
        tmpDeltaX,
        tmpDeltaY,
        this.scale
      )

      if (this.handle.includes('b')) {
        bottom = restrictToBounds(
          mouseClickPosition.bottom + deltaY,
          this.bounds.minBottom,
          this.bounds.maxBottom
        )

        if (this.lockAspectRatio && this.resizingOnY) {
          right = this.right - (this.bottom - bottom) * aspectFactor
        }
      } else if (this.handle.includes('t')) {
        top = restrictToBounds(
          mouseClickPosition.top - deltaY,
          this.bounds.minTop,
          this.bounds.maxTop
        )

        if (this.lockAspectRatio && this.resizingOnY) {
          left = this.left - (this.top - top) * aspectFactor
        }
      }

      if (this.handle.includes('r')) {
        right = restrictToBounds(
          mouseClickPosition.right + deltaX,
          this.bounds.minRight,
          this.bounds.maxRight
        )

        if (this.lockAspectRatio && this.resizingOnX) {
          bottom = this.bottom - (this.right - right) / aspectFactor
        }
      } else if (this.handle.includes('l')) {
        left = restrictToBounds(
          mouseClickPosition.left - deltaX,
          this.bounds.minLeft,
          this.bounds.maxLeft
        )

        if (this.lockAspectRatio && this.resizingOnX) {
          top = this.top - (this.left - left) / aspectFactor
        }
      }
      const width = computeWidth(this.parentWidth, left, right)
      const height = computeHeight(this.parentHeight, top, bottom)
      // if (this.onResize(this.handle, left, top, width, height) === false) {
      //   return
      // }
      let ratioH = height / this.originalHeight
      let ratioW = width / this.originalWidth
      this.transformScale = `scale(${ratioW},${ratioH})`
      this.left = left
      this.top = top
      this.right = right
      this.bottom = bottom
      this.width = width
      this.height = height
      // this.$emit('resizing')
      this.resizing = true
    },
    changeWidth(val) {
      // should calculate with scale 1.
      const [newWidth, _] = snapToGrid(this.grid, val, 0, 1)

      let right = restrictToBounds(
        this.parentWidth - newWidth - this.left,
        this.bounds.minRight,
        this.bounds.maxRight
      )
      let bottom = this.bottom

      if (this.lockAspectRatio) {
        bottom = this.bottom - (this.right - right) / this.aspectFactor
      }

      const width = computeWidth(this.parentWidth, this.left, right)
      const height = computeHeight(this.parentHeight, this.top, bottom)

      this.right = right
      this.bottom = bottom
      this.width = width
      this.height = height
    },
    changeHeight(val) {
      // should calculate with scale 1.
      const [_, newHeight] = snapToGrid(this.grid, 0, val, 1)

      let bottom = restrictToBounds(
        this.parentHeight - newHeight - this.top,
        this.bounds.minBottom,
        this.bounds.maxBottom
      )
      let right = this.right

      if (this.lockAspectRatio) {
        right = this.right - (this.bottom - bottom) * this.aspectFactor
      }

      const width = computeWidth(this.parentWidth, this.left, right)
      const height = computeHeight(this.parentHeight, this.top, bottom)

      this.right = right
      this.bottom = bottom
      this.width = width
      this.height = height
    },
    handleUp(e) {
      this.handle = null
      this.resetBoundsAndMouseState()
      this.dragEnable = false
      this.resizeEnable = false
      if (this.resizing) {
        this.resizing = false
        // this.$emit('resizestop')
        let part = {
          id: this.anovPart.id,
          style: {
            left: this.left + 'px',
            right: this.right + 'px',
            top: this.top + 'px',
            bottom: this.bottom + 'px',
            scale: this.transformScale
          }
        }
        app.pageServer.changePart(part)
      }
      if (this.dragging) {
        this.dragging = false
        let part = {
          id: this.anovPart.id,
          style: {
            left: this.left + 'px',
            right: this.right + 'px',
            top: this.top + 'px',
            bottom: this.bottom + 'px'
          }
        }
        app.pageServer.changePart(part)
      }
      app.$emit('anovPartChange', this.page)
      removeEvent(document.documentElement, eventsFor.move, this.handleResize)
    }
  },
  computed: {
    ...mapState('part', [
      'isDragOpen',
      'lockAspectRatio',
      'resizable',
      'draggable',
      'grid',
      'minW',
      'minH',
      'axis'
    ]),
    style() {
      return {
        transform: `translate(${this.left}px, ${this.top}px)${this.transformScale}`,
        transformOrigin: '0 0'
      }
    },
    actualHandles() {
      if (!this.resizable) return []
      return this.handles
    },
    scale() {
      return app.envServer.scaleRatio
    },
    resizingOnX() {
      return (
        Boolean(this.handle) &&
        (this.handle.includes('l') || this.handle.includes('r'))
      )
    },
    resizingOnY() {
      return (
        Boolean(this.handle) &&
        (this.handle.includes('t') || this.handle.includes('b'))
      )
    }
  }
}
